package com.wontube.smr.bo.sites.youtube;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.ibatis.session.SqlSession;

import com.wontube.client.extension.utils.FileUtils;
import com.wontube.client.extension.utils.StringUtils;
import com.wontube.core.dao.mybatis.QueryExample;
import com.wontube.core.pub.AppEnvironment;
import com.wontube.smr.bo.media.NativeMediaConverter;
import com.wontube.smr.bo.media.OnlineMp3Converter;
import com.wontube.smr.bo.sites.analizers.YouTubeVideoAnalizer;
import com.wontube.smr.client.model.po.Mp3Url;
import com.wontube.smr.client.model.po.VideoPage;
import com.wontube.smr.client.model.vo.AudioFile;
import com.wontube.smr.client.model.vo.VideoFile;
import com.wontube.smr.client.model.vo.VideoFiles;
import com.wontube.smr.dao.mapper.Mp3UrlMapper;
import com.wontube.smr.dao.mapper.VideoPageMapper;
import com.wontube.smr.model.vo.Progress;
import com.wontube.smr.pub.DefaultDBHelper;
import com.wontube.smr.pub.IObjectListener;
import com.wontube.smr.pub.SyncObject;

/**
 * @author yuechao
 * 
 */
public final class Mp3BO {
	private static final Hashtable<String, Mp3BO> boMap = new Hashtable<String, Mp3BO>();
	private static final Logger logger = LoggerFactory.getLogger(Mp3BO.class);

	private String pageUrl;
	private SyncObject syncRoot;

	private Mp3BO(String pageUrl) {
		this.pageUrl = pageUrl;
	}

	public static Mp3BO getBO(String pageUrl) {
		synchronized (boMap) {
			Mp3BO bo = boMap.get(pageUrl);
			if (bo == null) {
				bo = new Mp3BO(pageUrl);
				bo.syncRoot = new SyncObject();
				boMap.put(pageUrl, bo);
			}

			return bo;
		}
	}

	public SyncObject getSyncRoot() {
		return this.syncRoot;
	}

	public synchronized void addListener(IObjectListener<Progress> listener) {
		this.syncRoot.getListeners().add(listener);
	}

	public synchronized void removeListener(IObjectListener<Progress> listener) {
		this.syncRoot.getListeners().remove(listener);
	}

	/**
	 * 获取mp3
	 * 
	 * @param audioFile
	 * @return
	 */
	public boolean getMp3(AudioFile audioFile) {
		if (this.getMp3FromDatabase(audioFile)) { // 取缓存
			return true;
		}

		// 分析并提取,同一个地址需要同步，以免下载太多的相同文件
		try {
			this.syncRoot.increase();
			boolean hasQueue = this.syncRoot.getUseCount() > 1;

			synchronized (this.syncRoot) {
				// 如果进来时已经存在队列，再次查询
				if (hasQueue && this.getMp3FromDatabase(audioFile)) {
					return true;
				}

				if (!this.analyzeMp3Audio(audioFile)) {
					return false;
				}
			}
		} finally {
			this.syncRoot.decrease();
			if (this.syncRoot.getUseCount() < 1) {
				boMap.remove(this.pageUrl);
			}
		}

		return true;
	}

	private boolean getMp3FromDatabase(AudioFile audioFile) {
		SqlSession session = DefaultDBHelper.getSqlSession(false);
		List<Mp3Url> listMp3 = new ArrayList<Mp3Url>();
		List<VideoPage> listPage = new ArrayList<VideoPage>();
		try {
			VideoPageMapper pageMapper = session.getMapper(VideoPageMapper.class);
			QueryExample ex = new QueryExample();
			ex.or().addCriterion("page_url = ", pageUrl);
			listPage = pageMapper.selectByExample(ex);
			if (listPage.size() > 0) {
				VideoPage page = listPage.get(0);
				page.setVersion(Calendar.getInstance().getTime()); // 更新时间戳
				Mp3UrlMapper mp3Mapper = session.getMapper(Mp3UrlMapper.class);
				ex = new QueryExample();
				ex.or().addCriterion("page_id = ", page.getId());
				listMp3 = mp3Mapper.selectByExample(ex);

				if (listMp3.size() > 0) {
					// 检查mp3文件是否已经被删除
					Mp3Url mp3Url = listMp3.get(0);
					mp3Url.setVersion(Calendar.getInstance().getTime());
					String relativePath = StringUtils.format("mp3{}{}{}{}{}{}.{}", 
							File.separator,
							page.getSiteName(), 
							File.separator,
							mp3Url.getPath(), 
							File.separator,
							mp3Url.getId(), 
							mp3Url.getAudioFormat());
					String path = FileUtils.combinePath(AppEnvironment.dataPath, relativePath);

					File file = new File(path);
					if (!file.exists()) {
						// 文件不存在，需要重新获取
						ex = new QueryExample();
						ex.or().addCriterion("page_id=", page.getId());
						mp3Mapper.deleteByExample(ex);
						listMp3.clear();
					}
				}
			}
			session.commit();
		} finally {
			session.close();
		}

		if (listMp3.size() > 0) {
			Mp3Url mp3 = listMp3.get(0);
			audioFile.setId(mp3.getId());
			audioFile.setFilename(listPage.get(0).getTitle() + ".mp3");
			audioFile.setDownloadCount(mp3.getDownTimes());

			return true;
		}

		return false;
	}

	private boolean analyzeMp3Audio(AudioFile audioFile) {
		// 开始分析，进度为1%
		final Progress progress = new Progress("Analyzing", 0.01);
		this.syncRoot.onProgress(progress);

		YouTubeVideoAnalizer analizer = new YouTubeVideoAnalizer();
		VideoFiles videoFiles = new VideoFiles();
		if (!analizer.analyzeVideoUrls(videoFiles, this.pageUrl)) {
			return false;
		}
		VideoFile videoFile = getMp3Video(videoFiles);

		if (videoFile == null) {
			this.syncRoot.onError("There is no avalible aidio in the url.");
			return false;
		}

		// 在线转换视频
		String tempPath = FileUtils.combinePath(AppEnvironment.dataPath, "temp", analizer.getSiteName());
		File dirInfo = new File(tempPath);
		if (!dirInfo.exists()) {
			dirInfo.mkdirs();
		}
		String outputBase = FileUtils.combinePath(tempPath, UUID.randomUUID().toString());

		// 分析完成后，进度为2%
		progress.setMessage("Converting");
		progress.setPercent(2);
		this.syncRoot.onProgress(progress);

		OnlineMp3Converter converter = null;
		File file = null;
		try {
			// 现在已经完成2%, 转换完成后为　99%，因此在线转换权重为97
			converter = new OnlineMp3Converter(videoFile.getUrl(), outputBase);
			converter.setPercentWeight(97d);
			converter.addListener(new IObjectListener<Double>() {
				@Override
				public void onMessage(Double percent) {
					progress.setPercent(2 + percent);
					Mp3BO.this.syncRoot.onProgress(progress);
				}

				@Override
				public void onError(String msg) {
				}
			});

			long begin = System.currentTimeMillis();
			if (converter.extractStreams()) {
				file = converter.getOutputFile();
			}
			long cost = (System.currentTimeMillis() - begin) / 1000;
			logger.debug(this.pageUrl + " online convert cost time: {} s", cost);
		} catch (IOException e) {
			this.syncRoot.onError(e.getMessage());
			return false;
		} finally {
			if (converter != null) {
				converter.destroy();
			}
		}

		if (file == null) {
			this.syncRoot.onError("Error on extract mp3.");
			return false;
		}

		SimpleDateFormat format = new SimpleDateFormat("yyyyMM");
		String relativePath = format.format(Calendar.getInstance().getTime());

		String mp3Path = FileUtils.combinePath(AppEnvironment.dataPath, "mp3", analizer.getSiteName(), relativePath);
		dirInfo = new File(mp3Path);

		if (!dirInfo.exists()) {
			dirInfo.mkdirs();
		}

		long begin = System.currentTimeMillis();
		String mp3Filename = FileUtils.combinePath(mp3Path, file.getName());
		if (!NativeMediaConverter.toMp3(file.getPath(), mp3Filename, true)) {
			this.syncRoot.onError("Failed convert to mp3.");
			return false;
		}
		long cost = (System.currentTimeMillis() - begin) / 1000;
		logger.debug("{} native convert cost time: {} s", this.pageUrl, cost);

		mp3Filename = file.getName();
		int index = mp3Filename.lastIndexOf('.');
		if (index >= 0) {
			mp3Filename = mp3Filename.substring(0, index);
		}
		audioFile.setId(mp3Filename);
		audioFile.setFilename(videoFiles.getTitle() + ".mp3");
		audioFile.setDownloadCount(0L);

		// 存入数据库
		SqlSession session = DefaultDBHelper.getSqlSession(false);
		try {
			VideoPageMapper pageMapper = session.getMapper(VideoPageMapper.class);
			Mp3Url mp3Url = new Mp3Url();

			Date now = Calendar.getInstance().getTime();
			QueryExample ex = new QueryExample();
			ex.or().addCriterion("page_url = ", pageUrl);
			List<VideoPage> listPage = pageMapper.selectByExample(ex);
			if (listPage.size() > 0) {
				mp3Url.setPageId(listPage.get(0).getId());
			} else {
				// 插入page表
				VideoPage page = new VideoPage();
				page.setId(UUID.randomUUID().toString());
				page.setPageUrl(pageUrl);
				page.setSiteName(analizer.getSiteName());
				page.setTitle(videoFiles.getTitle());
				page.setCookie(videoFiles.getCookie());
				page.setCreateTime(now);
				page.setVersion(now);
				page.setVisitTimes(1L);
				pageMapper.insert(page);

				mp3Url.setPageId(page.getId());
			}

			// 把mp3 地址存入数据库
			Mp3UrlMapper mp3Mapper = session.getMapper(Mp3UrlMapper.class);
			mp3Url.setId(audioFile.getId());
			mp3Url.setAudioFormat("mp3");
			mp3Url.setDownTimes(0);

			mp3Url.setPath(relativePath);
			mp3Url.setCreateTime(now);
			mp3Url.setVersion(now);
			mp3Mapper.insert(mp3Url);

			session.commit();
		} finally {
			session.close();
		}

		// 完成
		progress.setPercent(100);
		this.syncRoot.onProgress(progress);

		return true;
	}

	private static VideoFile getMp3Video(VideoFiles videoFiles) {
		for (VideoFile videoFile : videoFiles.getVideos()) {
			int formatCode = videoFile.getFormatCode();
			if (formatCode == 5 || formatCode == 6) {
				return videoFile;
			}
		}

		return null;
	}
}
